package com.autumn.platform.dao.binding.handler.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.scripting.xmltags.OgnlCache;
import org.apache.ibatis.session.SqlSession;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;

import com.autumn.platform.core.Utils;
import com.autumn.platform.core.logger.Logs;
import com.autumn.platform.dao.annotation.BatchParam;
import com.autumn.platform.dao.annotation.Execute;
import com.autumn.platform.dao.annotation.Executes;
import com.autumn.platform.dao.annotation.SqlRef;
import com.autumn.platform.dao.binding.handler.IMapperHandlerContext;

public class BatchMapperHandler extends AbstractTransactionMapperHandler {

    public BatchMapperHandler() {
        super();
    }

    public BatchMapperHandler(int order) {
        super(order);
    }

    @Override
    public boolean supports(IMapperHandlerContext context) {
        return int[][].class.equals(context.getMethod().getReturnType())
                || int[].class.equals(context.getMethod().getReturnType());
    }

    /**
     * 批量执行
     * 
     * <pre>
     * 1. 一个SQL，不同参数执行多次
     * 2. 不同SQL，相同参数执行多次
     * 3. 不同SQL，对应各自不同的参数执行多次
     * 4. 以上情况的混合批量
     * </pre>
     */
    @Override
    public Object execute(SqlSession sqlSession, Object[] args, IMapperHandlerContext context) {
        final List<SqlIdAndParameter> sqlIdAndParameters = new ArrayList<SqlIdAndParameter>();

        Object param = context.getParamResolver().getNamedParams(args);

        Method method = context.getMethod();
        if (null != method.getAnnotation(Executes.class)) {//混合批量
            resolveExecutes(sqlIdAndParameters, param, method);
        } else {
            resolveMethod(sqlIdAndParameters, param, method, args);
        }
        return this.executeWithTransaction(sqlSession, context, new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                return executeBatch(sqlSession, sqlIdAndParameters, context);
            }
        });
    }

    /**
     * 执行批量
     * 
     * @param sqlSession
     * @param sqlIdAndParameters
     * @param context
     * @return
     */
    private Object executeBatch(SqlSession sqlSession, List<SqlIdAndParameter> sqlIdAndParameters, IMapperHandlerContext context) {
        for (SqlIdAndParameter sap : sqlIdAndParameters) {
            sqlSession.update(sap.sqlId, sap.parameter);
        }
        List<BatchResult> flushStatements = sqlSession.flushStatements();
        return resolveBatchResult(context, flushStatements);
    }

    /**
     * 解析批量结果
     * 
     * @param context
     * @param flushStatements
     * @return
     */
    private Object resolveBatchResult(IMapperHandlerContext context, List<BatchResult> flushStatements) {
        if (int[].class.equals(context.getMethod().getReturnType())) {
            int[] rs = new int[flushStatements.size()];
            for (int i = 0, size = flushStatements.size(); i < size; i++) {
                int count = 0;
                for (int uc : flushStatements.get(i).getUpdateCounts()) {
                    count += uc;
                }
                rs[i] = count;
            }
            return rs;
        } else {
            int[][] rs = new int[flushStatements.size()][];
            for (int i = 0, size = flushStatements.size(); i < size; i++) {
                rs[i] = flushStatements.get(i).getUpdateCounts();
            }
            return rs;
        }
    }

    /**
     * 解析混合批量参数
     * 
     * @param sqlIdAndParameters
     * @param param
     * @param method
     */
    private void resolveExecutes(List<SqlIdAndParameter> sqlIdAndParameters, Object param, Method method) {
        Execute[] executes = method.getAnnotation(Executes.class).value();
        for (Execute execute : executes) {
            String sqlId = resolveSqlId(method, execute.sqlRef());
            if (this.evaluateCondition(param, execute.condition())) {
                Object p = evaluateParam(param, execute.property());
                if (execute.batch()) {//本身又是一个批量，这时只能是一个SQL不同参数多次执行的批量
                    if (null == p) {
                        continue;
                    }
                    if (!this.isBatchParamType(p.getClass())) {
                        throw new IllegalStateException("interface method: [" + method + "], the param can't convert to batch param, sqlId:[" + sqlId + "]");
                    }
                    List<Object> list = Utils.convertToList(p, Object.class);
                    for (int i = 0, size = list.size(); i < size; i++) {
                        Object pp = getOneBatchParam(param, execute.name(), execute.index(), i, list.get(i));
                        sqlIdAndParameters.add(new SqlIdAndParameter(sqlId, pp));
                    }
                } else {//如果不是内嵌批量
                    sqlIdAndParameters.add(new SqlIdAndParameter(sqlId, p));
                }
            } else {
                Logs.debug("interface method: [" + method + "], not match condition:[" + execute.condition() + "], don't execute sqlId:[" + sqlId + "]");
            }
        }
    }

    /**
     * 解析批量参数
     * 
     * @param sqlIdAndParameters
     * @param param
     * @param method
     * @param args
     */
    private void resolveMethod(List<SqlIdAndParameter> sqlIdAndParameters, Object param, Method method, Object[] args) {
        BatchParam batchParamAnno = null;
        Object batchParam = param;

        //查找含有BatchParam参数的注解
        Annotation[][] paramAnnotations = method.getParameterAnnotations();
        outer: for (int i = 0, length = paramAnnotations.length; i < length; i++) {
            for (Annotation annotation : paramAnnotations[i]) {
                if (annotation instanceof BatchParam) {
                    batchParamAnno = (BatchParam) annotation;
                    batchParam = evaluateParam(args[i], batchParamAnno.property());
                    break outer;
                }
            }
        }

        String itemName = "item";
        String indexName = "index";
        boolean oneByOne = false;
        if (null != batchParamAnno) {
            itemName = batchParamAnno.name();
            indexName = batchParamAnno.index();
            oneByOne = batchParamAnno.oneByOne();
        }

        SqlRef sqlRef = method.getAnnotation(SqlRef.class);
        if (null == sqlRef || sqlRef.value().length <= 1) {//单SQL
            String sr = (null != sqlRef && sqlRef.value().length == 1) ? sqlRef.value()[0] : null;
            String sqlId = resolveSqlId(method, sr);// 一个SQL
            if (null == batchParam || !this.isBatchParamType(batchParam.getClass())) {
                throw new IllegalStateException("interface method: [" + method + "], the param can't convert to batch param, sqlId:[" + sqlId + "]");
            }
            List<Object> list = Utils.convertToList(batchParam, Object.class);
            for (int i = 0, size = list.size(); i < size; i++) {
                Object pp = getOneBatchParam(param, itemName, indexName, i, list.get(i));
                sqlIdAndParameters.add(new SqlIdAndParameter(sqlId, pp));
            }
        } else {//多SQL
            String[] sqlRefs = sqlRef.value();
            int length = sqlRefs.length;
            if (oneByOne) {//sqlId和参数一一对应
                if (null == batchParam || !this.isBatchParamType(batchParam.getClass())) {
                    throw new IllegalStateException("interface method: [" + method + "], the param can't convert to batch param");
                }
                List<Object> list = Utils.convertToList(batchParam, Object.class);
                if (length != list.size()) {
                    throw new IllegalStateException("interface method: [" + method + "], the batch sqlId's number is " + length + ", but the batch param's number is " + list.size() + "]");
                }
                for (int i = 0; i < length; i++) {
                    String sqlId = this.resolveSqlId(method, sqlRefs[i]);
                    Object pp = getOneBatchParam(param, itemName, indexName, i, list.get(i));
                    sqlIdAndParameters.add(new SqlIdAndParameter(sqlId, pp));
                }
            } else if (null != batchParam && this.isBatchParamType(batchParam.getClass())) {//参数是迭代类型
                List<Object> list = Utils.convertToList(batchParam, Object.class);
                for (int i = 0; i < length; i++) {
                    String sqlId = this.resolveSqlId(method, sqlRefs[i]);
                    for (int j = 0, size = list.size(); j < size; j++) {
                        Object pp = getOneBatchParam(param, itemName, indexName, j, list.get(j));
                        sqlIdAndParameters.add(new SqlIdAndParameter(sqlId, pp));
                    }
                }
            } else {//参数不是迭代类型，每次执行参数都相同
                for (int i = 0; i < length; i++) {
                    String sqlId = this.resolveSqlId(method, sqlRefs[i]);
                    sqlIdAndParameters.add(new SqlIdAndParameter(sqlId, batchParam));
                }
            }
        }
    }

    /**
     * 解析SqlId
     * 
     * @param method
     * @param sqlRef
     * @return
     */
    private String resolveSqlId(Method method, String sqlRef) {
        if (Utils.isBlank(sqlRef)) {
            return method.getDeclaringClass().getName() + "." + method.getName();
        } else {
            return method.getDeclaringClass().getName() + "." + sqlRef;
        }
    }

    /**
     * 获取批量中的一次执行的参数
     * 
     * @param commParam
     * @param batchParamName
     * @param batchIndexName
     * @param index
     * @param arg
     * @return
     */
    @SuppressWarnings("unchecked")
    private Object getOneBatchParam(Object commParam, String batchParamName, String batchIndexName, int index, Object arg) {
        final Map<String, Object> param = new HashMap<String, Object>();
        if (commParam instanceof Map) {
            param.putAll((Map<String, Object>) commParam);
        } else {
            param.put("param", commParam);
        }
        if (!Utils.isBlank(batchIndexName)) {
            param.put(batchIndexName, index);
        }
        if (!Utils.isBlank(batchParamName)) {
            param.put(batchParamName, arg);
        }
        return param;
    }

    /**
     * 求表达式的值
     * 
     * @param root
     * @param property
     * @return
     */
    private Object evaluateParam(Object root, String property) {
        if (!"this".equals(property) && !Utils.isBlank(property)) {
            Object rs = OgnlCache.getValue(property, root);
            //Object rs = SpEL.getValue(root, property);
            return rs;
        }
        return root;
    }

    private boolean evaluateCondition(Object root, String condition) {
        if (!Utils.isBlank(condition)) {
            Object c = OgnlCache.getValue(condition, root);
            if (!(c instanceof Boolean && ((Boolean) c).booleanValue())) {
                return false;
            }
        }
        return true;
        //return Utils.isBlank(condition) || SpEL.getValue(root, condition, boolean.class);
    }

    /**
     * 是否可转换为批量类型的参数
     * 
     * @param cls
     * @return
     */
    private boolean isBatchParamType(Class<?> cls) {
        return cls.isArray() || Iterator.class.isAssignableFrom(cls) || Enumeration.class.isAssignableFrom(cls)
                || Iterable.class.isAssignableFrom(cls); // 因此包含Collection，从而也包含List、Set、Queue等常见集合类型
    }

    /**
     * 辅助类
     */
    private class SqlIdAndParameter {
        String sqlId;
        Object parameter;

        SqlIdAndParameter(String sqlId, Object parameter) {
            this.sqlId = sqlId;
            this.parameter = parameter;
        }
    }
}
